#pragma once
#ifndef _KH_CORE_WEAKPTR_H
#define _KH_CORE_WEAKPTR_H
#include <Kharlia/Core/Common.h>

namespace kharlia { namespace core {

template <typename T> class TPtr;

/// Weak pointer to a Boost.Python instance and its underlying C++ object.
template <typename T>
class TWeakPtr {
    T* mPtr;
    handle<> mHandle;

public:
    typedef TWeakPtr<T> ThisType;

    TWeakPtr():
        mPtr(nullptr),
        mHandle() {}

    explicit TWeakPtr(PyObject* p):
        mPtr(p == nullptr ? nullptr : extract<T*>(p)),
        mHandle(p == nullptr ? handle<> : handle<>(PyWeakref_NewRef(p, NULL)))
    { 
        assert(mHandle.get() == NULL || PyWeakref_CheckRef(mHandle.get()));
    }

    explicit TWeakPtr(TPtr<T> const& p):
        mPtr(p.GetPtr()),
        mHandle(p ? handle<>(PyWeakref_NewRef(p.GetHandle().get(), NULL)) : handle<>()) {}

    template <typename U>
    ThisType& operator=(TWeakPtr<U> const& u) {
        mPtr = u.mPtr;
        mHandle = u.mHandle;
        assert(mHandle.get() == NULL || PyWeakref_CheckRef(mHandle.get()));
        return *this;
    }

    ThisType& operator=(TPtr<T> const& p) {
        mPtr = p.mPtr;
        mHandle = p ? handle<>(PyWeakref_NewRef(p.GetHandle().get(), NULL)) : handle<>();
        assert(mHandle.get() == NULL || PyWeakref_CheckRef(mHandle.get()));
        return *this;
    }

    void Reset() {
        mPtr = nullptr;
        mHandle.reset();
    }

    void Reset(PyObject* p) {
        if (p == nullptr)
            Reset();
        else {
            mPtr = extract<T*>(p);
            mHandle = handle<>(PyWeakref_NewRef(p, NULL));
        }
    }

    handle<> const& GetHandle() const {
        return mHandle;
    }

    operator bool() const {
        return mHandle.get() != nullptr &&
               PyWeakref_GET_OBJECT(mHandle.get()) != Py_None;
    }

    /// Create a pointer from this weak pointer.
    TPtr<T> Lock() const {
        if (!mHandle || PyWeakref_GET_OBJECT(mHandle.get()) == Py_None)
            return TPtr<T>();
        else {
            assert(mPtr != nullptr);
            //assert(mPtr == extract<T*>(PyWeakref_GET_OBJECT(mHandle.get())));
            return TPtr<T>(mPtr, borrowed(PyWeakref_GET_OBJECT(mHandle.get())));
        }
    }
};

} // namespace core

using core::TWeakPtr;

} // namespace kharlia

#endif // _KH_CORE_WEAKPTR_H